package org.netwia.networks;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.netwia.utils.DomainUtils;
import org.netwia.utils.ValidationUtils;

/**
 * Defines the network environment. Network environment is the network with environment objects.
 * Environment objects are objects defined in the map, where key is the name of the object and value is the object.
 * Network can point to these objects.
 * 
 * @author radek.hecl
 *
 * @param <T> network parameter type
 */
public class NetworkEnvironment<T> {

    /**
     * Builder object.
     *
     * @param <T> network parameter type
     */
    public static class Builder<T> {

        /**
         * Environment objects.
         */
        private Map<String, Object> environment = new HashMap<String, Object>();

        /**
         * Meta data about the whole environment.
         */
        private Map<String, String> metaData = new HashMap<String, String>();
        
        /**
         * Network.
         */
        private Network<T> network;
        
        /**
         * Sets the environment.
         * 
         * @param environment environment
         * @return this instance
         */
        public Builder<T> setEnvironment(Map<String, Object> environment) {
            this.environment = DomainUtils.softCopyMap(environment);
            return this;
        }
        
        /**
         * Add object to the environment.
         * 
         * @param name name of the object
         * @param object object to the environment
         * @return this instance
         */
        public Builder<T> addEnvironment(String name, Object object) {
            this.environment.put(name, object);
            return this;
        }        
        
        /**
         * Sets the meta data.
         * 
         * @param metaData meta data
         * @return this instance
         */
        public Builder<T> setMetaData(Map<String, String> metaData) {
            this.metaData = DomainUtils.softCopyMap(metaData);
            return this;
        }
        
        /**
         * Adds meta data value.
         * 
         * @param name data field name
         * @param value data field value
         * @return this instance
         */
        public Builder<T> addMetaData(String name, String value) {
            this.metaData.put(name, value);
            return this;
        }
        
        /**
         * Sets the network.
         * 
         * @param network network
         * @return this instance
         */
        public Builder<T> setNetwork(Network<T> network) {
            this.network = network;
            return this;
        }
        
        /**
         * Builds the result object.
         * 
         * @return created object
         */
        public NetworkEnvironment<T> build() {
            return new NetworkEnvironment<T>(this);
        }
    }

    /**
     * Environment objects.
     */
    private Map<String, Object> environment = new HashMap<String, Object>();

    /**
     * Meta data about the whole environment.
     */
    private Map<String, String> metaData = new HashMap<String, String>();
    
    /**
     * Network.
     */
    private Network<T> network;

    /**
     * Creates new instance.
     * 
     * @param builder builder object
     */
    public NetworkEnvironment(Builder<T> builder) {
        environment = Collections.unmodifiableMap(DomainUtils.softCopyMap(builder.environment));
        metaData = Collections.unmodifiableMap(DomainUtils.softCopyMap(builder.metaData));
        network = builder.network;
        guardInvariants();
    }

    /**
     * Guards this object to be consistent. Throws exception if this is not the case.
     */
    private void guardInvariants() {
        ValidationUtils.guardNotNullMap(environment, "environment cannot have null element");
        ValidationUtils.guardNotEmptyStringInCollection(environment.keySet(), "environment must have all keys not empty");
        ValidationUtils.guardNotNullMap(metaData, "metaData cannot have null element");
        ValidationUtils.guardNotEmptyStringInCollection(metaData.keySet(), "metaData must have all keys not empty");
        ValidationUtils.guardNotNull(network, "network cannot be null");
    }

    /**
     * Returns the environment objects.
     * 
     * @return environment objects
     */
    public Map<String, Object> getEnvironment() {
        return environment;
    }
    
    /**
     * Returns the meta data.
     * 
     * @return meta data
     */
    public Map<String, String> getMetaData() {
        return metaData;
    }
    
    /**
     * Returns network.
     * 
     * @return network
     */
    public Network<T> getNetwork() {
        return network;
    }
    
    @Override
    public int hashCode() {
        return HashCodeBuilder.reflectionHashCode(this);
    }

    @Override
    public boolean equals(Object obj) {
        return EqualsBuilder.reflectionEquals(this, obj);
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this);
    }
    
}
